package cc.verywell.pureblock.msg.nfs.strc;

import java.util.ArrayList;
import java.util.List;

import cc.verywell.pureblock.log.Pblog;
import cc.verywell.pureblock.msg.nfs.strc.NfsStructureV1.DataLink;
import cc.verywell.pureblock.msg.nfs.strc.NfsStructureV1.Sign;
import cc.verywell.pureblock.msg.nfs.strc.NfsStructureV1.Sub;
import cc.verywell.pureblock.net.AioProvider;
import cc.verywell.pureblock.util.sign.Sha;

/**
* 搞了8个小时?
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年10月3日 上午4:19:58
*/
public class NfsStructureV1encoder {
	public static boolean DEBUG = true;

	final public long offset;
	final public long length;
	final public ExtraBag extra;
	
	public NfsStructureV1encoder(long datasize, List<Sub> alias, List<Sign> signs, List<DataLink> extralinks) {
		this(datasize, alias, signs, extralinks, Sha.getInstance().length(), AioProvider.FILE_MAX_SIZE, false);
	}
	/**
	 * 
	 * @param datasize 数据大小
	 * @param alias 别名组
	 * @param signs 签名组
	 * @param extralinks 外部文件组
	 * @param HASH_SIZE 文件HASH长度
	 * @param BAG_SIZE 每个包最大大小
	 * @param PUREDATA_ISOLATE 当文件为单个文件时,是否优先将纯数据制作成独立包?
	 */
	public NfsStructureV1encoder(long datasize, List<Sub> alias, List<Sign> signs, List<DataLink> extralinks,final int HASH_SIZE,final int BAG_SIZE,final boolean PUREDATA_ISOLATE) {
		final boolean hasExtra;
		if(alias==null && signs==null && extralinks==null) {
			hasExtra = false;
			if(datasize<=0) throw new IllegalArgumentException(); //没有任何内容的文件不应该通过
		}else {
			hasExtra = true;
		}
		ArrayList<NfsStructureV1encoder> al = new ArrayList<>();
		///首选算出一个满载数据包的容量;
		final long PURE_DATA_CAPACITY = BAG_SIZE - NfsStructureV1.STRUCTURE_HEAD_SIZE; //减去文件头可以获得纯数据的大小;
		long data_offset = 0;
		long data_rest = datasize;
		while(data_rest>PURE_DATA_CAPACITY) {
			al.add(new NfsStructureV1encoder(data_offset,PURE_DATA_CAPACITY,null));
			data_offset += PURE_DATA_CAPACITY;
			data_rest -= PURE_DATA_CAPACITY;
		}
		///算出如果尾部数据单独组成一个包需要的引用大小;
		int iso_size = (al.isEmpty()?NfsStructureV1.DataLink.ARRAY_HEAD_SIZE:0) + 1*(NfsStructureV1.DataLink.SINGLE_EXTRA_SIZE+HASH_SIZE);
		boolean iso_ready = data_rest>iso_size;
		///尝试看看1个文件能否塞完
		onefile:{
			long databag = 0;
			if(!al.isEmpty()) {
				databag += NfsStructureV1.DataLink.ARRAY_HEAD_SIZE + al.size()*(NfsStructureV1.DataLink.SINGLE_EXTRA_SIZE+HASH_SIZE);
				if(databag>PURE_DATA_CAPACITY)break onefile;
			}
			if(hasExtra) {
				if(alias!=null) {
					databag += NfsStructureV1.Sub.ARRAY_HEAD_SIZE;
					for (EncodeAble ea : alias) {
						databag += ea.encodeSize();
					}
				}
				if(signs!=null) {
					databag += NfsStructureV1.Sign.ARRAY_HEAD_SIZE;
					for (EncodeAble ea : signs) {
						databag += ea.encodeSize();
					}
				}
				if(extralinks!=null) {
					databag += NfsStructureV1.DataLink.ARRAY_HEAD_SIZE;
					for (EncodeAble ea : extralinks) {
						databag += ea.encodeSize();
					}
				}
			}
			if(iso_ready && ( (hasExtra && PUREDATA_ISOLATE) || (databag+data_rest>PURE_DATA_CAPACITY && databag+iso_size<=PURE_DATA_CAPACITY) ) ){
				//这个判断是，尾部单独一个包后，可以把参数都塞进一个文件
				iso_ready = false;
				al.add(new NfsStructureV1encoder(data_offset,data_rest,null));
				data_offset += data_rest;
				data_rest -= data_rest;
				databag += iso_size;
			}
			if(databag+data_rest>PURE_DATA_CAPACITY) break onefile;
			////单文件测试通过 - 开始组装;
			this.offset = data_offset;
			this.length = data_rest;
			if(!al.isEmpty() || hasExtra) {
				this.extra = new ExtraBag(data_offset, al, alias, signs, extralinks);
			}else extra = null;
			if(DEBUG)Pblog.DEBUG("NfsStructureV1encoder bags:",al.size()+1);
			return;
		}
		////需要多层文件组合;
		iso_size = 1*(NfsStructureV1.DataLink.SINGLE_EXTRA_SIZE+HASH_SIZE);
		iso_ready = data_rest>iso_size;
		long exrta_rest = 0;
		boolean extra_ready = false;
		ExtraBag extra_bag = null;
		if(hasExtra) { //封合额外文件
			
			int aliasOffset,signsOffset,extralinksOffset;
			aliasOffset=signsOffset=extralinksOffset=0;
			long databag = 0;
			boolean first = false;
			
			while(!first) {
				List<Sub> aliasSubList = null;
				List<Sign> signsSubList = null;
				List<DataLink> extralinksSubList = null;
				
				List<? extends EncodeAble> ea;
				int eaOffset;
				int headSize;
				
				databag = 0;
				boolean togo = true;
				first = true;
				
				ea = alias;
				eaOffset = aliasOffset;
				headSize = NfsStructureV1.Sub.ARRAY_HEAD_SIZE;
				if(togo && ea!=null && ea.size()>eaOffset) {
					databag += headSize;
					int i = eaOffset;
					for(;i<ea.size();i++) {
						int size = ea.get(i).encodeSize();
						databag += size;
						if(databag>PURE_DATA_CAPACITY) {
							if(first && eaOffset==i) throw new IllegalArgumentException();
							togo = false;
							break;
						}
					}
					first = false;
					aliasSubList = alias.subList(eaOffset, i);
					aliasOffset = i;
				}
				
				ea = signs;
				eaOffset = signsOffset;
				headSize = NfsStructureV1.Sign.ARRAY_HEAD_SIZE;
				if(togo && ea!=null && ea.size()>eaOffset) {
					databag += headSize;
					int i = eaOffset;
					for(;i<ea.size();i++) {
						int size = ea.get(i).encodeSize();
						databag += size;
						if(databag>PURE_DATA_CAPACITY) {
							if(first && eaOffset==i) throw new IllegalArgumentException();
							togo = false;
							break;
						}
					}
					first = false;
					signsSubList = signs.subList(eaOffset, i);
					signsOffset = i;
				}

				ea = extralinks;
				eaOffset = extralinksOffset;
				headSize = NfsStructureV1.DataLink.ARRAY_HEAD_SIZE;
				if(togo && ea!=null && ea.size()>eaOffset) {
					databag += headSize;
					int i = eaOffset;
					for(;i<ea.size();i++) {
						int size = ea.get(i).encodeSize();
						databag += size;
						if(databag>PURE_DATA_CAPACITY) {
							if(first && eaOffset==i) throw new IllegalArgumentException();
							togo = false;
							break;
						}
					}
					first = false;
					extralinksSubList = extralinks.subList(eaOffset, i);
					extralinksOffset = i;
				}
				
				extra_bag = new ExtraBag(0, null, aliasSubList, signsSubList, extralinksSubList);
				if(!togo){
					al.add(new NfsStructureV1encoder(0, 0, extra_bag));
					extra_bag = null;
				}else {
					exrta_rest = databag;
					extra_ready = exrta_rest>iso_size;
					break;
				}
			}
		}
		/////现在所有的原始文件都在AL里面了，按矩阵平摊即可;
		long maxdatabag //每个包最大可以放多少个链接
			= /*3;//测试参数*/(PURE_DATA_CAPACITY-NfsStructureV1.DataLink.ARRAY_HEAD_SIZE)/(NfsStructureV1.DataLink.SINGLE_EXTRA_SIZE+HASH_SIZE);
		///------------------------------预先策略
		//Pblog.DEBUG("links: +",extra_bag.extralinks==null?"null":extra_bag.extralinks.size());

		if(PUREDATA_ISOLATE) {
			if(DEBUG)Pblog.DEBUG("using PUREDATA_ISOLATE pack tactics");
			if(extra_ready) { //封合余下参数
				extra_ready = false;
				exrta_rest = 0;
				al.add(new NfsStructureV1encoder(0, 0, extra_bag));
				extra_bag = null;
			}
			if(iso_ready || (data_rest>0 && 0==al.size()%maxdatabag)) { //封合余下数据
				iso_ready = false;
				al.add(new NfsStructureV1encoder(data_offset,data_rest,null));
				data_offset += data_rest;
				data_rest -= data_rest;
			}
		}else{
			long databag_tail_count = al.size()%maxdatabag;
			long databag_tail_rest //末尾包还剩下多少空间
				= PURE_DATA_CAPACITY-NfsStructureV1.DataLink.ARRAY_HEAD_SIZE-(databag_tail_count)*(NfsStructureV1.DataLink.SINGLE_EXTRA_SIZE+HASH_SIZE);
			//if(DEBUG)Pblog.DEBUG("dataBagTailCount:",databag_tail_count," databag_tail_rest:",databag_tail_rest," exrta_rest:",exrta_rest," extra_bag:",extra_bag," data_rest:",data_rest);
			if(databag_tail_count>0 && databag_tail_rest>exrta_rest+data_rest) {
				if(DEBUG)Pblog.DEBUG("using 末尾空间够大，什么都不用加 pack tactics");
				///末尾空间够大，什么都不用加;
			}else if(extra_ready && databag_tail_rest>iso_size+data_rest) {
				if(DEBUG)Pblog.DEBUG("using 仅打包参数即可满足末尾需求 pack tactics");
				//仅打包参数即可满足末尾需求;	
				extra_ready = false;
				exrta_rest = 0;
				al.add(new NfsStructureV1encoder(0, 0, extra_bag));
				extra_bag = null;
			}else if(iso_ready && databag_tail_rest>exrta_rest+iso_size) {
				if(DEBUG)Pblog.DEBUG("using 仅打包数据即可满足末尾需求 pack tactics");
				//仅打包数据即可满足末尾需求;
				iso_ready = false;
				al.add(new NfsStructureV1encoder(data_offset,data_rest,null));
				data_offset += data_rest;
				data_rest -= data_rest;
			}else if(extra_bag!=null && PURE_DATA_CAPACITY<=exrta_rest+data_rest){
				if(DEBUG)Pblog.DEBUG("using 可以把数据和参数合并为同一个文件 pack tactics");
				//可以把数据和参数合并为同一个文件;
				al.add(new NfsStructureV1encoder(data_offset, data_rest, extra_bag));
				
				extra_ready = false;
				exrta_rest = 0;
				extra_bag = null;
				
				iso_ready = false;
				data_offset += data_rest;
				data_rest -= data_rest;
			}else { //TODO 这样就够了吗?
				if(DEBUG)Pblog.DEBUG("using 这样就够了吗? pack tactics");
				if(extra_ready || (extra_bag!=null && 0==al.size()%maxdatabag) ) { //封合余下参数
					extra_ready = false;
					exrta_rest = 0;
					al.add(new NfsStructureV1encoder(0, 0, extra_bag));
					extra_bag = null;
				}
				if(iso_ready || (data_rest>0 && 0==al.size()%maxdatabag) ) { //封合余下数据
					iso_ready = false;
					al.add(new NfsStructureV1encoder(data_offset,data_rest,null));
					data_offset += data_rest;
					data_rest -= data_rest;
				}
			}
		}
		int bagsCount = al.size();
		//------------------封合策略完成
		if(al.size()>maxdatabag) {
			if(DEBUG)Pblog.DEBUG("using 多层封装  pack enddo");
			//首层组装
			{
				ArrayList<NfsStructureV1encoder> strcAl = new ArrayList<NfsStructureV1encoder>();
				long size = 0;
				int eaOffset = 0;
				int eaPosition = 0;
				for (NfsStructureV1encoder ec : al) {
					size += ec.length;
					if(ec.extra!=null)size += ec.extra.datasize;
					if(++eaPosition==maxdatabag) {
						strcAl.add(new NfsStructureV1encoder(0, 0, new ExtraBag(size, al.subList(eaOffset, eaOffset+eaPosition), null, null, null)));
						size = 0;
						eaOffset += eaPosition;
						eaPosition = 0;
					}
				}
				if(eaOffset!=al.size() || extra_bag!=null || data_rest>0) { ///在首层完成所有封合;
					if(DEBUG)Pblog.DEBUG("using 在首层完成所有封合  pack enddo");
					List<NfsStructureV1encoder> eaAl;
					if(eaOffset!=al.size())eaAl = al.subList(eaOffset, al.size());
					else eaAl = null;
					
					ExtraBag ea;
					if(extra_bag!=null)ea = new ExtraBag(size, eaAl, extra_bag.alias, extra_bag.signs, extra_bag.extralinks);
					else ea = new ExtraBag(size, eaAl, null, null, null);
					
					extra_ready = false;
					exrta_rest = 0;
					extra_bag = null;

					strcAl.add(new NfsStructureV1encoder(data_offset, data_rest, ea));
					
					iso_ready = false;
					data_offset += data_rest;
					data_rest -= data_rest;
				}
				bagsCount += strcAl.size();
				al = strcAl;
			}
			//下层组装
			while(al.size()>maxdatabag) {
				ArrayList<NfsStructureV1encoder> strcAl = new ArrayList<NfsStructureV1encoder>();
				long size = 0;
				int eaOffset = 0;
				int eaPosition = 0;
				for (NfsStructureV1encoder ec : al) {
					size += ec.length;
					if(ec.extra!=null)size += ec.extra.datasize;
					if(++eaPosition==maxdatabag) {
						strcAl.add(new NfsStructureV1encoder(0, 0, new ExtraBag(size, al.subList(eaOffset, eaOffset+eaPosition), null, null, null)));
						size = 0;
						eaOffset += eaPosition;
						eaPosition = 0;
					}
				}
				if(eaOffset!=al.size()) { 
					strcAl.add(new NfsStructureV1encoder(0, 0, new ExtraBag(size, al.subList(eaOffset, al.size()), null, null, null)));
				}
				bagsCount += strcAl.size();
				al = strcAl;
			}
		}else {
			if(DEBUG)Pblog.DEBUG("using 未超出需求  pack enddo");
		}
		//最后封合
		this.offset = data_offset;
		this.length = data_rest;
		if(!al.isEmpty() || hasExtra) {
			if(extra_bag!=null)this.extra = new ExtraBag(data_offset, al, extra_bag.alias, extra_bag.signs, extra_bag.extralinks);
			else this.extra = new ExtraBag(data_offset, al, null, null, null);
		}else extra = null;
		if(DEBUG)Pblog.DEBUG("NfsStructureV1encoder bags:",bagsCount+1);
	}

	
	
	private NfsStructureV1encoder(long offset,long length, ExtraBag extra) {
		this.offset = offset;
		this.length = length;
		this.extra = extra;
	}
	
	static public class ExtraBag{
		final public long datasize;
		final public List<NfsStructureV1encoder> datalinks;
		final public List<Sub> alias;
		final public List<Sign> signs;
		final public List<DataLink> extralinks;
		public ExtraBag(long datasize,  List<NfsStructureV1encoder> datalinks,
				List<Sub> alias, List<Sign> signs, List<DataLink> extralinks) {
			super();
			this.datasize = datasize;
			this.datalinks = datalinks;
			this.alias = alias;
			this.signs = signs;
			this.extralinks = extralinks;
		}
	}

}
